package org.ch02.test00.demo08.teacherWork;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.*;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author CSY
 * @date 2025 - 03 - 21
 */
public class DbUtil {
    /**
     *定义Set集合 - 收集所有的类型转换器
     */
    private static final Set<String> CONVERT_HANDLERS = new HashSet<>();

    /**
     * 注册类型转换器 - todo:Java SPI - 解耦
     */
    static {
        CONVERT_HANDLERS.add("org.ch02.test00.demo08.teacherWork.converter.DateConverter");
        CONVERT_HANDLERS.add("org.ch02.test00.demo08.teacherWork.converter.LocalDateConverter");
        CONVERT_HANDLERS.add("org.ch02.test00.demo08.teacherWork.converter.LocalDateTimeConverter");
        CONVERT_HANDLERS.add("org.ch02.test00.demo08.teacherWork.converter.LocalTimeConverter");
    }
    /**
     * 条件查询某张数据表
     * @param beanClass 查询的实体
     * @param cond 条件
     * @return
     */
    public static Object select4Bean(Class<?> beanClass, Map<String, Object> cond) {
        // 第一：定义要操作数据库的 SQL 语句
        String sql = SqlGenerate.generateSelect(beanClass, cond);

        // 第二：获取连接对象
        Connection conn = DruidUtil.getConnection();

        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            // 第三：预编译 SQL 语句
            preparedStatement = conn.prepareStatement(sql);

            // 第四：填充参数
            setParameter(cond, preparedStatement);

            // 第五：执行查询操作 - resultSet 中，存储着当前查询的数据
            resultSet = preparedStatement.executeQuery();

            // 第六：把结果集中的数据映射到实体对象中，并返回 - 基于反射（内省）技术，实现对象的映射
            if(resultSet.next()) {
                return getBean(beanClass, resultSet);
            }
        } catch (Exception e) {
            throw new RuntimeException("查询失败!", e);
        } finally {
            // 关闭对象
            DruidUtil.close(resultSet, preparedStatement, conn);
        }

        return null;
    }

    private static Object getBean(Class<?> beanClass, ResultSet resultSet) throws SQLException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, NoSuchFieldException, ClassNotFoundException {
        // 1.获取元数据对象 - 获取结果集的相关信息
        ResultSetMetaData metaData = resultSet.getMetaData();

        // 2.基于元数据对象获取总列数
        int columnCount = metaData.getColumnCount();

        // 3.实例化 Bean 对象
        Object bean = beanClass.getConstructor().newInstance();

        // 4.循环遍历各列
        for (int i = 1; i <= columnCount; i++) {
            // 5.基于元数据对象，获取查询表的列名称 - sta_pos -> staPos
            String columnLabel = metaData.getColumnLabel(i);

            // 6.根据字段名称，获取各列数据
            //结果集获取数据的数据类型与实体对象属性的数据类型不一致，导致异常
            Object data = resultSet.getObject(columnLabel);

            // 7.基于数据表字段名称，通过反射获取实体对象对应 Field 对象
            Field field = beanClass.getDeclaredField(SqlGenerate.convertToCamel(columnLabel));

            if (data != null) {
                //遍历所有的类型转换器
                for (String convertHandler : CONVERT_HANDLERS) {
                    System.out.println(1);
                    //基于反射，实例化类型转换器
                    TypeConverter convert = (TypeConverter)Class.forName(convertHandler).getConstructor().newInstance();
                    //判断是否需要转换 - 字段
                    if (convert.supports(field.getType())) {
                        //需要转换，进行转换
                        data = convert.apply(field.getType(), data);
                    }
                }
            }

            //判断Bean对象的当前字段为基本类型 并且获取到的值为null
            if(field.getType().isPrimitive() && data == null) {
                continue;
            }
            // 8.初始化 Bean 对象 - 把查询的字段数据映射到Bean对象相应的属性中
            field.setAccessible(true);
            field.set(bean,data);
        }

        // 9.返回初始化的Bean对象
        return bean;
    }

    /**
     * 填充参数
     * @param cond 填充的参数
     * @param preparedStatement
     * @throws SQLException
     */
    private static void setParameter(Map<String, Object> cond, PreparedStatement preparedStatement) throws SQLException {
        int i = 1;
        for (Map.Entry<String, Object> entry : cond.entrySet()) {
            preparedStatement.setObject(i++, entry.getValue());
        }
    }
}
